﻿using System;
using System.ComponentModel;
using System.Dynamic;
using System.Net;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace Ecobee.Net
{
	[JsonConverter(typeof(CamelCaseStringEnumConverter))]
	public enum ModelNumber
	{
		None,
		IdtSmart,
		IdtEms,
		SiSmart,
		SiEms
	}

	[JsonObject(Id = "thermostat")]
	public class Thermostat : SummaryRevision
	{
		#region Private Fields
		private DateTime _dataTimestamp = DateTime.Now;
		private Runtime _runtime = new Runtime();
		private Settings _settings = new Settings();
		private Weather _weather = new Weather();
		private Location _location = new Location();
		private ThermostatProgram _program = new ThermostatProgram();
		private EventList _eventList = new EventList();
		private AlertList _alertList = new AlertList();
		private bool _isRegistered = false;
		private ModelNumber _modelNumber = ModelNumber.None;
		private string _serialNumber = string.Empty;
		private string _productCode = string.Empty;
		private DateTime _lastModified = DateTime.MinValue;
		#endregion

		public Thermostat()
			: base()
		{
			throw new Exception("Construct this object by specifying the API instance to use.");
		}

		/// <summary>
		/// Gets the active Ecobee.Net.Api object.
		/// </summary>
		public Thermostat(Api api)
			: base(api)
		{
		}

		/// <summary>
		/// Gets the Unique Identifier for this object.
		/// </summary>
		[JsonIgnore]
		public override string UniqueKey
		{
			get
			{
				return this.Identifier;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		[JsonProperty(PropertyName = "modelNumber", DefaultValueHandling = DefaultValueHandling.Ignore)]
		[DefaultValue(ModelNumber.None)]
		public ModelNumber ModelNumber
		{
			get
			{
				return _modelNumber;
			}
			set
			{
				_modelNumber = value;
			}
		}

		/// <summary>
		/// Whether the user registered the thermostat. 
		/// </summary>
		[JsonProperty(PropertyName = "isRegistered", DefaultValueHandling = DefaultValueHandling.Ignore)]
		[DefaultValue(false)]
		public bool IsRegistered
		{
			get
			{
				return _isRegistered;
			}
			set
			{
				_isRegistered = value;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		[JsonProperty(PropertyName = "serialNumber", DefaultValueHandling = DefaultValueHandling.Ignore)]
		[DefaultValue("")]
		public string SerialNumber
		{
			get
			{
				return _serialNumber;
			}
			set
			{
				_serialNumber = value;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		[JsonProperty(PropertyName = "productCode", DefaultValueHandling = DefaultValueHandling.Ignore)]
		[DefaultValue("")]
		public string ProductCode
		{
			get
			{
				return _productCode;
			}
			set
			{
				_productCode = value;
			}
		}

		/// <summary>
		/// The last modified date time for the thermostat configuration. 
		/// </summary>
		[JsonProperty(PropertyName = "lastModified", DefaultValueHandling = DefaultValueHandling.Ignore)]
		public DateTime LastModified
		{
			get
			{
				return _lastModified;
			}
			set
			{
				_lastModified = value;
			}
		}

		/// <summary>
		/// Gets the Date and Time of the last update from the API.
		/// </summary>
		[JsonIgnore]
		public DateTime DataTimestamp
		{
			get
			{
				return _dataTimestamp;
			}
			internal set
			{
				_dataTimestamp = value;
			}
		}

		/// <summary>
		/// The runtime object represents the last known thermostat running state. This state is composed from 
		/// the last interval status message received from a thermostat. It is also updated each time the 
		/// thermostat posts configuration changes to the server. The runtime object is read-only.
		/// </summary>
		[JsonIgnore]
		public Runtime Runtime
		{
			get
			{
				return _runtime;
			}
			set
			{
				_runtime = value;
			}
		}

		/// <summary>
		/// The Settings contains all the configuration properties of the thermostat.
		/// </summary>
		[DefaultValueConverter(typeof(Settings), typeof(SettingsDefaultConverter), "")]
		public Settings Settings
		{
			get
			{
				return _settings;
			}
			set
			{
				_settings = value;
			}
		}

		/// <summary>
		/// The current thermostat weather forecast.
		/// </summary>
		[JsonIgnore]
		public Weather Weather
		{
			get
			{
				return _weather;
			}
			set
			{
				_weather = value;
			}
		}

		/// <summary>
		/// The Location describes the physical location and coordinates of the thermostat as entered by the thermostat 
		/// owner. The address information is used in a geocode look up to obtain the thermostat coordinates. The 
		/// coordinates are used to obtain accurate weather information.
		/// </summary>
		[JsonIgnore]
		public Location Location
		{
			get
			{
				return _location;
			}
			set
			{
				_location = value;
			}
		}

		[JsonIgnore]
		public ThermostatProgram Program
		{
			get
			{
				return _program;
			}
			set
			{
				_program = value;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		[JsonIgnore]
		public EventList Events
		{
			get
			{
				return _eventList;
			}
			set
			{
				_eventList = value;
			}
		}

		/// <summary>
		/// The thermostat's unacknowledged Alert objects.
		/// </summary>
		[JsonIgnore]
		public AlertList Alerts
		{
			get
			{
				return _alertList;
			}
			set
			{
				_alertList = value;
			}
		}

		#region Actions
		public async Task<bool> LoadDetails(Selection selection)
		{
			bool returnValue = false;

			// ***
			// *** Always override these properties so that
			// *** only the the data for the current thermostat
			// *** is retrieved.
			// ***
			selection.SelectionType = SelectionType.Thermostats;
			selection.SelectionMatch = this.Identifier;

			// ***
			// *** A Thermostats object is required to request
			// *** details for one or more thermostats.
			// ***
			GetThermostatDetail thermostatUpdate = new GetThermostatDetail()
			{
				Selection = selection
			};

			// ***
			// *** Call the web service
			// ***
			ApiResult response = await this.Api.Http.Get(this.Api.KnownUri.Thermostat(thermostatUpdate));

			if (response.Successful)
			{
				if (response.DataObject != null && response.DataObject.thermostatList != null)
				{
					this.ApplyDetails(selection, response.DataObject.thermostatList[0]);
					returnValue = true;
				}
			}
			else
			{
				response.ThrowException();
			}

			return returnValue;
		}

		public async Task<bool> AcknowledgeAlert(Alert alert, AcknowledgeType acknowledgeType)
		{
			bool returnValue = false;

			// ***
			// *** Build the data structure to call Acknowledge in the API
			// ***
			FunctionList functions = new FunctionList()
			{
				Selection = new Selection()
				{
					SelectionType = SelectionType.Thermostats,
					SelectionMatch = this.Identifier
				},
				Functions = new Function[] { Function.Acknowledge(
													new Acknowledge()
													{
														ThermostatIdentifier = this.Identifier,
														AcknowledgeRef = alert.AcknowledgeRef,
														AcknowledgeType = acknowledgeType,
														RemindMeLater = (acknowledgeType == AcknowledgeType.Defer)
													})
												}
			};

			ApiResult response = await this.Api.Http.Post(this.Api.KnownUri.Thermostat(), functions);

			// ***
			// *** Check if the call was successful
			// ***
			if (response.Successful)
			{
				// ***
				// *** Remove this from the collection; it will be gone in the
				// *** next update for this thermostat
				// ***
				this.Alerts.Remove(alert);
				returnValue = true;
			}
			else
			{
				response.ThrowException();
			}

			return returnValue;
		}

		public async Task<bool> SendMessage(string text)
		{
			bool returnValue = false;

			// ***
			// *** Build the data structure to call Acknowledge in the API
			// ***
			FunctionList functions = new FunctionList()
			{
				Selection = new Selection()
				{
					SelectionType = SelectionType.Thermostats,
					SelectionMatch = this.Identifier
				},
				Functions = new Function[] { Function.SendMessage(
													new Message()
													{
														Text = text.Limit(500)
													})
												}
			};

			ApiResult response = await this.Api.Http.Post(this.Api.KnownUri.Thermostat(), functions);

			if (response.Successful)
			{
				returnValue = true;
			}
			else
			{
				response.ThrowException();
			}

			return returnValue;
		}

		public async Task<bool> SetHold(Hold hold)
		{
			bool returnValue = false;

			// ***
			// *** Build the data structure to call Acknowledge in the API
			// ***
			FunctionList functions = new FunctionList()
			{
				Selection = new Selection()
				{
					SelectionType = SelectionType.Thermostats,
					SelectionMatch = this.Identifier
				},
				Functions = new Function[] { Function.SetHold(hold) }
			};

			ApiResult response = await this.Api.Http.Post(this.Api.KnownUri.Thermostat(), functions);

			if (response.Successful)
			{
				returnValue = true;
			}
			else
			{
				response.ThrowException();
			}

			return returnValue;
		}

		public async Task<bool> ResumeProgram()
		{
			bool returnValue = false;

			// ***
			// *** Build the data structure to call Acknowledge in the API
			// ***
			FunctionList functions = new FunctionList()
			{
				Selection = new Selection()
				{
					SelectionType = SelectionType.Thermostats,
					SelectionMatch = this.Identifier
				},
				Functions = new Function[] { Function.ResumeProgram() }
			};

			ApiResult response = await this.Api.Http.Post(this.Api.KnownUri.Thermostat(), functions);

			if (response.Successful)
			{
				returnValue = true;
			}
			else
			{
				response.ThrowException();
			}

			return returnValue;
		}

		public async Task<bool> Update(Thermostat thermostat)
		{
			bool returnValue = false;

			// ***
			// *** Build the data structure to call Acknowledge in the API
			// ***
			ThermostatUpdate update = new ThermostatUpdate()
			{
				Selection = new Selection()
				{
					SelectionType = SelectionType.Thermostats,
					SelectionMatch = this.Identifier
				},
				Thermostat = thermostat
			};

			ApiResult response = await this.Api.Http.Post(this.Api.KnownUri.Thermostat(), update);

			if (response.Successful)
			{
				returnValue = true;
			}
			else
			{
				response.ThrowException();
			}

			return returnValue;
		}
		#endregion

		protected override string OnDeserializeKeyValue(dynamic dataObject)
		{
			return dataObject.identifier;
		}

		internal void ApplyDetails(Selection selection, dynamic data)
		{
			this.LoadFrom(data);

			if (selection.IncludeRuntime)
			{
				if (data.runtime != null)
				{
					this.Runtime.LoadFrom(data.runtime);
				}
			}

			if (selection.IncludeSettings)
			{
				if (data.settings != null)
				{
					this.Settings.LoadFrom(data.settings);
				}
			}

			if (selection.IncludeEvents)
			{
				if (data.events != null)
				{
					this.Events.Load(data.events);
				}
			}

			if (selection.IncludeAlerts)
			{
				if (data.alerts != null)
				{
					this.Alerts.Load(data.alerts);
				}
			}

			if (selection.IncludeWeather)
			{
				if (data.weather != null)
				{
					this.Weather.LoadFrom(data.weather);
				}
			}

			if (selection.IncludeLocation)
			{
				if (data.location != null)
				{
					this.Location.LoadFrom(data.location);
				}
			}

			if (selection.IncludeProgram)
			{
				if (data.program != null)
				{
					this.Program.LoadFrom(data.program);
				}
			}

			//includeExtendedRuntime
			//includeElectricity
			//includeLocation
			//includeDevice
			//includeTechnician
			//includeUtility
			//includeManagement

			this.DataTimestamp = DateTime.Now;
		}

		/// <summary>
		/// Returns a string that represents the current object
		/// that can be used for display purposes.
		/// </summary>
		/// <returns>A string that represents the current object.</returns>
		public override string ToString()
		{
			return this.Name;
		}
	}
}
